/*
 *	PacManClone.cc
 * 
 *	Pac Man Clone. La duh!
 *
 *	Coded by Joseph A. Marrero
 *	1/20/2008
 */
#include <cassert>
#include <iostream>
#include <sstream>
#include <GL/glut.h>
#include <cassert>
#include <fstream>
#include <sstream>
#include <ImageIO.h>
#include <iomanip>
#include "PacManGame.h"
#include "Block.h"
#include "Empty.h"
#include "SmallDot.h"
#include "BigDot.h"
#include "PacMan.h"
#include "RedGhost.h"
#include "OrangeGhost.h"
#include "PinkGhost.h"
#include "CyanGhost.h"


using namespace std;

unsigned int gWidth = 0, gHeight = 0;


#include <cstdlib>
#include <cstdio>

int main( int argc, char *argv[] )
{
	glutInit( &argc, argv );
	glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_MULTISAMPLE );

#ifdef _DEBUG	
	glutInitWindowSize( Game::RESOLUTION_WIDTH, Game::RESOLUTION_HEIGHT );
	glutCreateWindow( "Pac Man" );
#else // release	
	ostringstream oss;
	oss << Game::RESOLUTION_WIDTH << "x" << Game::RESOLUTION_HEIGHT << ":32@" << Game::FRAMES_PER_SECOND;
	glutGameModeString( oss.str( ).c_str( ) );

	if( glutGameModeGet( GLUT_GAME_MODE_POSSIBLE ) )
		glutEnterGameMode( );
	else
	{
		oss.clear( );
		oss << Game::RESOLUTION_WIDTH << "x" << Game::RESOLUTION_HEIGHT << "@" << Game::FRAMES_PER_SECOND;

		glutGameModeString( oss.str( ).c_str( ) );
		if( glutGameModeGet( GLUT_GAME_MODE_POSSIBLE ) )
			glutEnterGameMode( );
		else
		{
			cerr << "The requested mode is not available!" << endl;
			return -1;	
		}
	}
#endif


	glutDisplayFunc( render );
	glutReshapeFunc( resize );
	glutKeyboardFunc( keyboard_keypress );
	glutSpecialFunc( keyboard_special_keypress );
	glutIdleFunc( idle );
	//glutTimerFunc( Game::UPDATE_FREQUENCY /* a tenth of a second */, update, 0 );

	initialize( );
	glutMainLoop( );
	deinitialize( );

	return 0;
}


void initialize( )
{
	glDisable( GL_ALPHA_TEST );
	
	glEnable( GL_POINT_SMOOTH );
	glEnable( GL_LINE_SMOOTH );
	glHint( GL_POINT_SMOOTH_HINT, GL_NICEST );
	glHint( GL_LINE_SMOOTH_HINT, GL_NICEST );
	glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
	
	glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	

	glShadeModel( GL_FLAT );
	glClearDepth( 1.0f );		
	glDisable( GL_DEPTH_TEST );


	glPixelStorei( GL_PACK_ALIGNMENT, 4 );
	glPixelStorei( GL_UNPACK_ALIGNMENT, 4 );

	// Initialize IL
	ImageIO::initialize( );


	glEnable( GL_TEXTURE_2D );

	// bof load image assets...
	ImageIO::Result result = ImageIO::FAILURE;

	for( int i = 0; i < 7; i++ )
	{
		ostringstream oss;
		oss << "assets/block" << setw( 2 ) << setfill( '0' ) << i << ".tga";
		result = ImageIO::loadImage( &Game::tgaBlockImage[ i ], oss.str( ).c_str( ), ImageIO::TARGA );
		assert( result == ImageIO::SUCCESS );
	}

	result = ImageIO::loadImage( &Game::tgaBigDotImage, "assets/big_dot.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaSmallDotImage, "assets/small_dot.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaCyanGhostImage, "assets/ghost_cyan.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaRedGhostImage, "assets/ghost_red.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaPinkGhostImage, "assets/ghost_pink.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaOrangeGhostImage, "assets/ghost_orange.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaGhostFleeImages[ 0 ], "assets/ghost_flee1.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaGhostFleeImages[ 1 ], "assets/ghost_flee2.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaGhostEyesImage, "assets/ghost_eyes.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaPacManImage, "assets/pacman.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaHud, "assets/hud.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaMainMenu, "assets/main_menu.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaStart, "assets/start.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaExit, "assets/exit.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaGameOver, "assets/game_over.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	result = ImageIO::loadImage( &Game::tgaNextLevel, "assets/next_level.tga", ImageIO::TARGA );
	assert( result == ImageIO::SUCCESS );

	// eof load image assets...

	// bof set up game textures...

	for( int i = 0; i < 7; i++ )
	{
		glGenTextures( 1, &Game::nBlockTexture[ i ] );
		glBindTexture( GL_TEXTURE_2D, Game::nBlockTexture[ i ] );
		glTexImage2D( GL_TEXTURE_2D, 0, 3, Game::tgaBlockImage[ i ].width, Game::tgaBlockImage[ i ].height, 0, GL_RGB, GL_UNSIGNED_BYTE, Game::tgaBlockImage[ i ].pixelData );
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	}

	glGenTextures( 1, &Game::nBigDotTexture );
	glBindTexture( GL_TEXTURE_2D, Game::nBigDotTexture );
	glTexImage2D( GL_TEXTURE_2D, 0, 4, Game::tgaBigDotImage.width, Game::tgaBigDotImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Game::tgaBigDotImage.pixelData );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glGenTextures( 1, &Game::nSmallDotTexture );
	glBindTexture( GL_TEXTURE_2D, Game::nSmallDotTexture );
	glTexImage2D( GL_TEXTURE_2D, 0, 4, Game::tgaSmallDotImage.width, Game::tgaSmallDotImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Game::tgaSmallDotImage.pixelData );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glGenTextures( 1, &Game::nCyanGhostTexture );
	glBindTexture( GL_TEXTURE_2D, Game::nCyanGhostTexture );
	glTexImage2D( GL_TEXTURE_2D, 0, 4, Game::tgaCyanGhostImage.width, Game::tgaCyanGhostImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Game::tgaCyanGhostImage.pixelData );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glGenTextures( 1, &Game::nRedGhostTexture );
	glBindTexture( GL_TEXTURE_2D, Game::nRedGhostTexture );
	glTexImage2D( GL_TEXTURE_2D, 0, 4, Game::tgaRedGhostImage.width, Game::tgaRedGhostImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Game::tgaRedGhostImage.pixelData );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glGenTextures( 1, &Game::nPinkGhostTexture );
	glBindTexture( GL_TEXTURE_2D, Game::nPinkGhostTexture );
	glTexImage2D( GL_TEXTURE_2D, 0, 4, Game::tgaPinkGhostImage.width, Game::tgaPinkGhostImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Game::tgaPinkGhostImage.pixelData );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glGenTextures( 1, &Game::nOrangeGhostTexture );
	glBindTexture( GL_TEXTURE_2D, Game::nOrangeGhostTexture );
	glTexImage2D( GL_TEXTURE_2D, 0, 4, Game::tgaOrangeGhostImage.width, Game::tgaOrangeGhostImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Game::tgaOrangeGhostImage.pixelData );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glGenTextures( 1, &Game::nGhostFleeTextures[ 0 ] );
	glBindTexture( GL_TEXTURE_2D, Game::nGhostFleeTextures[ 0 ] );
	glTexImage2D( GL_TEXTURE_2D, 0, 4, Game::tgaGhostFleeImages[ 0 ].width, Game::tgaGhostFleeImages[ 0 ].height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Game::tgaGhostFleeImages[ 0 ].pixelData );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glGenTextures( 1, &Game::nGhostFleeTextures[ 1 ] );
	glBindTexture( GL_TEXTURE_2D, Game::nGhostFleeTextures[ 1 ] );
	glTexImage2D( GL_TEXTURE_2D, 0, 4, Game::tgaGhostFleeImages[ 1 ].width, Game::tgaGhostFleeImages[ 1 ].height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Game::tgaGhostFleeImages[ 1 ].pixelData );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	
	glGenTextures( 1, &Game::nGhostEyesTexture );
	glBindTexture( GL_TEXTURE_2D, Game::nGhostEyesTexture );
	glTexImage2D( GL_TEXTURE_2D, 0, 4, Game::tgaGhostEyesImage.width, Game::tgaGhostEyesImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Game::tgaGhostEyesImage.pixelData );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glGenTextures( 1, &Game::nPacManTexture );
	glBindTexture( GL_TEXTURE_2D, Game::nPacManTexture );
	glTexImage2D( GL_TEXTURE_2D, 0, 4, Game::tgaPacManImage.width, Game::tgaPacManImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Game::tgaPacManImage.pixelData );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );

	glGenTextures( 1, &Game::nHud );
	glBindTexture( GL_TEXTURE_2D, Game::nHud );
	glTexImage2D( GL_TEXTURE_2D, 0, 3, Game::tgaHud.width, Game::tgaHud.height, 0, GL_RGB, GL_UNSIGNED_BYTE, Game::tgaHud.pixelData );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );

	glGenTextures( 1, &Game::nMainMenu );
	glBindTexture( GL_TEXTURE_2D, Game::nMainMenu );
	glTexImage2D( GL_TEXTURE_2D, 0, 3, Game::tgaMainMenu.width, Game::tgaMainMenu.height, 0, GL_RGB, GL_UNSIGNED_BYTE, Game::tgaMainMenu.pixelData );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
 
	glGenTextures( 1, &Game::nStart );
	glBindTexture( GL_TEXTURE_2D, Game::nStart );
	glTexImage2D( GL_TEXTURE_2D, 0, 4, Game::tgaStart.width, Game::tgaStart.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Game::tgaStart.pixelData );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );

	glGenTextures( 1, &Game::nExit );
	glBindTexture( GL_TEXTURE_2D, Game::nExit );
	glTexImage2D( GL_TEXTURE_2D, 0, 4, Game::tgaExit.width, Game::tgaExit.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Game::tgaExit.pixelData );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	
	glGenTextures( 1, &Game::nGameOver );
	glBindTexture( GL_TEXTURE_2D, Game::nGameOver );
	glTexImage2D( GL_TEXTURE_2D, 0, 3, Game::tgaGameOver.width, Game::tgaGameOver.height, 0, GL_RGB, GL_UNSIGNED_BYTE, Game::tgaGameOver.pixelData );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	
	glGenTextures( 1, &Game::nNextLevel );
	glBindTexture( GL_TEXTURE_2D, Game::nNextLevel );
	glTexImage2D( GL_TEXTURE_2D, 0, 3, Game::tgaNextLevel.width, Game::tgaNextLevel.height, 0, GL_RGB, GL_UNSIGNED_BYTE, Game::tgaNextLevel.pixelData );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	// eof set up game textures...


	Game::nHudDisplayList = glGenLists( 1 );
	glNewList( Game::nHudDisplayList, GL_COMPILE );
		glBegin( GL_QUADS );
			glTexCoord2i( 0, 0 ); glVertex2i( 0, 0 );
			glTexCoord2i( 1, 0 ); glVertex2i( Game::HUD_WIDTH, 0 );
			glTexCoord2i( 1, 1 ); glVertex2i( Game::HUD_WIDTH, Game::HUD_HEIGHT );
			glTexCoord2i( 0, 1 ); glVertex2i( 0, Game::HUD_HEIGHT );
		glEnd( );
	glEndList( );


	Object::initialize( ); 

	glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );

	/*
	 * Sound Setup
	 */
	FMOD_RESULT      fmodResult;
	unsigned int     version;
	FMOD_SPEAKERMODE speakermode;
	FMOD_CAPS        caps;

	/*
		Create a System object and initialize.
	*/
	fmodResult = FMOD::System_Create(&Game::soundSystem);
	ERRCHECK(fmodResult);

	fmodResult = Game::soundSystem->getVersion(&version);
	ERRCHECK(fmodResult);

	if (version < FMOD_VERSION)
	{
		printf("Error!  You are using an old version of FMOD %08x.  This program requires %08x\n", version, FMOD_VERSION);
		deinitialize( );
		exit( EXIT_FAILURE );
	}
	
	fmodResult = Game::soundSystem->setPluginPath( "assets/plugins/" );
	ERRCHECK(fmodResult);
	
	fmodResult = Game::soundSystem->getDriverCaps(0, &caps, 0, 0, &speakermode);	
	ERRCHECK(fmodResult);

	fmodResult = Game::soundSystem->setSpeakerMode(speakermode);       /* Set the user selected speaker mode. */
	ERRCHECK(fmodResult);

	if (caps & FMOD_CAPS_HARDWARE_EMULATED)             /* The user has the 'Acceleration' slider set to off!  This is really bad for latency!. */
	{                                                   /* You might want to warn the user about this. */
		fmodResult = Game::soundSystem->setDSPBufferSize(1024, 10);    /* At 48khz, the latency between issuing an fmod command and hearing it will now be about 213ms. */
		ERRCHECK(fmodResult);
	}

	fmodResult = Game::soundSystem->init(100, FMOD_INIT_NORMAL, 0);    /* Replace with whatever channel count and flags you use! */
	if (fmodResult == FMOD_ERR_OUTPUT_CREATEBUFFER)         /* Ok, the speaker mode selected isn't supported by this soundcard.  Switch it back to stereo... */
	{
		fmodResult = Game::soundSystem->setSpeakerMode(FMOD_SPEAKERMODE_STEREO);
		ERRCHECK(fmodResult);
	            
		fmodResult = Game::soundSystem->init(100, FMOD_INIT_NORMAL, 0); /* Replace with whatever channel count and flags you use! */
		ERRCHECK(fmodResult);
	}

	/*
	 * Load sounds and music
	 * Notes: FMOD_DEFAULT uses the defaults.  These are the same as FMOD_LOOP_OFF | FMOD_2D | FMOD_HARDWARE.
	 */
	fmodResult = Game::soundSystem->createSound( "assets/sfx/next_level.wav", FMOD_DEFAULT, 0, &Game::soundEffects[ Game::SFX_NEXT_LEVEL ] );	
	ERRCHECK(fmodResult);	
	fmodResult = Game::soundSystem->createSound( "assets/sfx/game_over.wav", FMOD_DEFAULT, 0, &Game::soundEffects[ Game::SFX_GAME_OVER ] );	
	ERRCHECK(fmodResult);
	fmodResult = Game::soundSystem->createSound( "assets/sfx/small_dot.wav", FMOD_DEFAULT, 0, &Game::soundEffects[ Game::SFX_SMALL_DOT_EATEN ] );	
	ERRCHECK(fmodResult);
	fmodResult = Game::soundSystem->createSound( "assets/sfx/big_dot.wav", FMOD_DEFAULT, 0, &Game::soundEffects[ Game::SFX_BIG_DOT_EATEN ] );	
	ERRCHECK(fmodResult);
	fmodResult = Game::soundSystem->createSound( "assets/sfx/entity_killed.wav", FMOD_DEFAULT, 0, &Game::soundEffects[ Game::SFX_GHOST_OR_PACMAN_EATEN ] );	
	ERRCHECK(fmodResult);
	
	fmodResult = Game::soundSystem->createSound( "assets/msc/menu.mid", FMOD_LOOP_NORMAL | FMOD_2D | FMOD_HARDWARE, 0, &Game::music );	
	ERRCHECK(fmodResult);

	
	fmodResult = Game::soundSystem->playSound( FMOD_CHANNEL_FREE, Game::music, false, &Game::musicChannel );
	ERRCHECK(fmodResult);
	Game::musicChannel->setVolume( 0.5f );
}

void deinitialize( )
{
	Game::deinitializeLevel( );
	Object::deinitialize( );


	// release sound subsystem...
	Game::soundSystem->release( );


	// delete textures from video memory	
	for( int i = 0; i < 7; i++ )
		glDeleteTextures( 1, &Game::nBlockTexture[ i ] );

	glDeleteTextures( 1, &Game::nBigDotTexture );
	glDeleteTextures( 1, &Game::nSmallDotTexture );
	glDeleteTextures( 1, &Game::nCyanGhostTexture );
	glDeleteTextures( 1, &Game::nRedGhostTexture );
	glDeleteTextures( 1, &Game::nPinkGhostTexture );
	glDeleteTextures( 1, &Game::nOrangeGhostTexture );
	glDeleteTextures( 1, &Game::nGhostFleeTextures[ 0 ] );
	glDeleteTextures( 1, &Game::nGhostFleeTextures[ 1 ] );
	glDeleteTextures( 1, &Game::nPacManTexture );
	glDeleteTextures( 1, &Game::nMainMenu ); 
	glDeleteTextures( 1, &Game::nStart );
	glDeleteTextures( 1, &Game::nExit );	
	glDeleteTextures( 1, &Game::nGameOver );	
	glDeleteTextures( 1, &Game::nNextLevel );

	for( int i = 0; i < 7; i++ )
		ImageIO::destroyImage( &Game::tgaBlockImage[ i ] );

	ImageIO::destroyImage( &Game::tgaBigDotImage );
	ImageIO::destroyImage( &Game::tgaSmallDotImage );
	ImageIO::destroyImage( &Game::tgaCyanGhostImage );
	ImageIO::destroyImage( &Game::tgaRedGhostImage );
	ImageIO::destroyImage( &Game::tgaPinkGhostImage );
	ImageIO::destroyImage( &Game::tgaOrangeGhostImage );
	ImageIO::destroyImage( &Game::tgaGhostFleeImages[ 0 ] );
	ImageIO::destroyImage( &Game::tgaGhostFleeImages[ 1 ] );
	ImageIO::destroyImage( &Game::tgaPacManImage );
	ImageIO::destroyImage( &Game::tgaMainMenu );
	ImageIO::destroyImage( &Game::tgaStart );
	ImageIO::destroyImage( &Game::tgaExit );
	ImageIO::destroyImage( &Game::tgaGameOver );
	ImageIO::destroyImage( &Game::tgaNextLevel );
}


void render( )
{
	static unsigned int nFrame = 0;
	static int time = 0;
	static int timeBase = 0;
	static unsigned int nFramesPerSeconds = 0;

	nFrame++;

	time = glutGet( GLUT_ELAPSED_TIME );
	
	if( time - timeBase > 1000 ) {
		nFramesPerSeconds = nFrame * 1000.0/(time-timeBase);
	 	timeBase = time;		
		nFrame = 0;
	}

	if( nFramesPerSeconds > Game::FRAMES_PER_SECOND )
		return;

	if( nFrame % 6 == 0 )
	{
		update( 0 );
	}
	//cout << nFramesPerSeconds << endl;

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity( );
	
	// TO DO: Drawing code goes here...
	switch( Game::state )
	{
		case Game::MAIN_MENU:
			Game::renderMainMenu( );
			break;
		case Game::IN_GAME:
			Game::renderInGame( );
			break;
		case Game::GAME_OVER:
			Game::renderGameOver( );
			break;
		case Game::NEXT_LEVEL:
			Game::renderNextLevel( );
			break;
		default:
			assert( false ); // bad game state
	}

#ifdef _DEBUG
	GLenum error = glGetError( );
	const GLubyte *pErrorString = gluErrorString( error );
	//cout << pErrorString << endl;
#endif
	
	glutSwapBuffers( );
	
	// update sound subsystem...
	Game::soundSystem->update( );
}

void resize( int width, int height )
{
	//glViewport( 0, 0, width, height );
	glViewport( 0, 0, Game::RESOLUTION_WIDTH, Game::RESOLUTION_HEIGHT );
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity( );
	
	gWidth = width;
	gHeight = height;

	if( height == 0 ) height = 1;

	static float aspect = (float) width / (float) height;
		
	//glOrtho( 0.0, Game::GAME_BOARD_SIZE * Game::BLOCK_WIDTH * aspect, 0.0, Game::GAME_BOARD_SIZE * Game::BLOCK_HEIGHT, 0.0, 3.0 );
	glOrtho( 0.0, Game::RESOLUTION_WIDTH, 0.0, Game::RESOLUTION_HEIGHT, 0.0, 3.0 );
	glMatrixMode( GL_MODELVIEW );
}

void keyboard_keypress( unsigned char key, int x, int y )
{
	switch( key )
	{
		case ESC:
			deinitialize( );
			exit( 0 );
			break;
		default:
			break;
	}

	switch( Game::state )
	{
		case Game::MAIN_MENU:
			Game::handleMainMenuInput( (int) key );
			break;
		case Game::IN_GAME:
			Game::handleInGameInput( (int) key );
			break;
		case Game::NEXT_LEVEL:
			Game::handleNextLevelInput( (int) key );
			break;
		case Game::GAME_OVER:
			Game::handleGameOverInput( (int) key );
			break;
		default:
			assert( false ); // bad game state
	}

}

void keyboard_special_keypress( int key, int x, int y )
{
	switch( Game::state )
	{
		case Game::MAIN_MENU:
			Game::handleMainMenuInput( key );
			break;
		case Game::IN_GAME:
			Game::handleInGameInput( key );
			break;
		case Game::NEXT_LEVEL:
			Game::handleNextLevelInput( key );
			break;
		case Game::GAME_OVER:
			Game::handleGameOverInput(key);
			break;
		default:
			assert( false ); // bad game state
	}

}

void idle( )
{ glutPostRedisplay( ); }

void writeText( void *font, std::string &text, int x, int y, float r, float g, float b )
{
	int width = glutGet( (GLenum) GLUT_WINDOW_WIDTH );
	int height = glutGet( (GLenum) GLUT_WINDOW_HEIGHT );

	glPushAttrib( GL_LIGHTING_BIT | GL_TEXTURE_BIT | GL_DEPTH_BUFFER_BIT | GL_CURRENT_BIT );
		glDisable( GL_DEPTH_TEST );
		glDisable( GL_TEXTURE_2D );
		glDisable( GL_LIGHTING );

		glMatrixMode( GL_PROJECTION );
		glPushMatrix( );
			glLoadIdentity( );	
			glOrtho( 0, width, 0, height, 1.0, 10.0 );
				
			glMatrixMode( GL_MODELVIEW );
			glPushMatrix( );
				glLoadIdentity( );
				glColor3f( r, g, b );
				glTranslatef( 0.0f, 0.0f, -1.0f );
				glRasterPos2i( x, y );

				for( unsigned int i = 0; i < text.size( ); i++ )
					glutBitmapCharacter( font, text[ i ] );
				
			glPopMatrix( );
			glMatrixMode( GL_PROJECTION );
		glPopMatrix( );
		glMatrixMode( GL_MODELVIEW );
	glPopAttrib( );
}







void update( int n )
{
	//glutPostRedisplay( );

	switch( Game::state )
	{
		case Game::MAIN_MENU:
			break;
		case Game::IN_GAME:
			Game::updateInGame( );
			break;
		case Game::NEXT_LEVEL:
			break;
		case Game::GAME_OVER:
			break;
		default:
			assert( false ); // bad game state
	}


	//glutTimerFunc( Game::UPDATE_FREQUENCY /* a tenth of a second */, update, 0 );
}


// Pac Man Game stuff
namespace Game 
{
	ImageIO::Image tgaBlockImage[ 7 ];
	ImageIO::Image tgaBigDotImage;
	ImageIO::Image tgaSmallDotImage;
	ImageIO::Image tgaCyanGhostImage;
	ImageIO::Image tgaRedGhostImage;
	ImageIO::Image tgaPinkGhostImage;
	ImageIO::Image tgaOrangeGhostImage;
	ImageIO::Image tgaGhostFleeImages[ 2 ];
	ImageIO::Image tgaGhostEyesImage;
	ImageIO::Image tgaPacManImage;
	ImageIO::Image tgaHud;	
	ImageIO::Image tgaMainMenu;
	ImageIO::Image tgaStart;
	ImageIO::Image tgaExit;
	ImageIO::Image tgaGameOver;
	ImageIO::Image tgaNextLevel;

	
	FMOD::System *soundSystem = NULL;
	FMOD::Sound *soundEffects[ 5 ] = {0};
	FMOD::Sound *music = NULL;
	FMOD::Channel *musicChannel = NULL;


	unsigned int nBlockTexture[ 7 ] = {0};
	unsigned int nBigDotTexture = 0;
	unsigned int nSmallDotTexture = 0;
	unsigned int nCyanGhostTexture = 0;
	unsigned int nRedGhostTexture = 0;
	unsigned int nPinkGhostTexture = 0;
	unsigned int nOrangeGhostTexture = 0;
	unsigned int nGhostFleeTextures[ 2 ] = {0};
	unsigned int nGhostEyesTexture = 0;
	unsigned int nPacManTexture;
	unsigned int nMainMenu = 0;
	unsigned int nHud = 0;
	unsigned int nStart = 0;
	unsigned int nExit = 0;
	unsigned int nGameOver = 0;
	unsigned int nNextLevel = 1;

	unsigned int nHudDisplayList = 0;

	unsigned int nMainMenuSelection = 0;

	unsigned int corners[ 4 ][ 2 ] = {
		{ 0, 0 },
		{ 0, Game::GAME_BOARD_SIZE - 1 },
		{ Game::GAME_BOARD_SIZE - 1, 0 },
		{ Game::GAME_BOARD_SIZE - 1, Game::GAME_BOARD_SIZE - 1 }
	};

	GameState state = Game::MAIN_MENU;
	std::vector<Object *> gameBoard;
	unsigned int nLevel = 1;
	unsigned int nDotCount = 0;
	unsigned int nNumberOfGhosts = 0;
	unsigned int nScore = 0;
	int nPlayerLives = 3; // start with 3 lives
	PacMan *pPlayer = NULL;
	Object *ghosts[ 4 ];

void loadLevel( const char *filename )
{
	cout << "Loading " << filename << "..." << endl;
	ifstream fin( filename, ios::in );

	unsigned int nPacManCount = 0;

	unloadLevel( );

	if( fin.fail( ) )
	{
		assert( false );
		return;
	}

	// initialize level
	nDotCount = 0;
	nNumberOfGhosts = 0;
	string line( "" );
	unsigned int lineCount = 0;


	while( !fin.eof( ) )
	{
		getline( fin, line );

		

		for( unsigned int i = 0; i < line.length( ); i++ )
		{
			unsigned int index = lineCount * GAME_BOARD_SIZE + i;
			Object *pObj = NULL;

			switch( line[ i ] )
			{
				case '#':
					pObj = new Block( );
					break;
				case 'D':
					pObj = new BigDot( );
					nDotCount++;
					break;
				case 'R': // red ghost
					pObj = new Empty( );
					ghosts[ RED_GHOST ] = new RedGhost( );
					ghosts[ RED_GHOST ]->X = i;
					ghosts[ RED_GHOST ]->Y = lineCount;
					nNumberOfGhosts++;
					break;
				case 'O': // orange ghost
					pObj = new Empty( );
					ghosts[ ORANGE_GHOST ] = new OrangeGhost( );
					ghosts[ ORANGE_GHOST ]->X = i;
					ghosts[ ORANGE_GHOST ]->Y = lineCount;
					nNumberOfGhosts++;
					break;
				case 'P': // pink ghost
					pObj = new Empty( );
					ghosts[ PINK_GHOST ] = new PinkGhost( );
					ghosts[ PINK_GHOST ]->X = i;
					ghosts[ PINK_GHOST ]->Y = lineCount;
					nNumberOfGhosts++;
					break;
				case 'C': // cyan ghost
					pObj = new Empty( );
					ghosts[ CYAN_GHOST ] = new CyanGhost( );
					ghosts[ CYAN_GHOST ]->X = i;
					ghosts[ CYAN_GHOST ]->Y = lineCount;
					nNumberOfGhosts++;
					break;
				case 'X': // Pac man
					pObj = new Empty( );
					pPlayer = new PacMan( );
					pPlayer->X = i;
					pPlayer->Y = lineCount;
					nPacManCount++;
					if( nPacManCount > 1 ) assert( false ); // only one pac man allowed...
					break;
				case ' ':
				case 'd':
					pObj = new SmallDot( );
					nDotCount++;
					break;
				default:
					pObj = new Empty( );
					break; // ignore all other chars
			}
			
			
			pObj->X = i;
			pObj->Y = lineCount;

			assert( pObj != NULL );
			gameBoard.push_back( pObj );
		}
		lineCount++;
	}
}

void unloadLevel( )
{
	cout << "Unloading level..." << endl;


	delete pPlayer;

	for( unsigned int i = 0; i < nNumberOfGhosts; i++ )
		delete ghosts[ i ];

	for( unsigned int i = 0; i < gameBoard.size( ); i++ )
	{
		delete gameBoard[ i ];
	}

	gameBoard.clear( );
}




void renderMainMenu( )
{
	glMatrixMode( GL_PROJECTION );
	glPushMatrix( );
	glLoadIdentity( );
	glOrtho( 0.0, gWidth, 0.0, gHeight, 0.0, 1.0 );
	glMatrixMode( GL_MODELVIEW );

	glPushMatrix( );
		glLoadIdentity( );

		glPushAttrib( GL_CURRENT_BIT | GL_TEXTURE_BIT );
			glBindTexture( GL_TEXTURE_2D, Game::nMainMenu );

			glBegin( GL_QUADS );
				glTexCoord2i( 0, 0 ); glVertex2i( 0, 0 );
				glTexCoord2i( 1, 0 ); glVertex2i( gWidth, 0 );
				glTexCoord2i( 1, 1 ); glVertex2i( gWidth, gHeight );
				glTexCoord2i( 0, 1 ); glVertex2i( 0, gHeight );
			glEnd( );
		glPopAttrib( );
	glPopMatrix( );


	glPushAttrib( GL_CURRENT_BIT | GL_TEXTURE_BIT  );
		if( nMainMenuSelection == 1 ) glColor4f( 0.5f, 0.5f, 0.5f, 1.0f );
		else glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );

		glPushMatrix( );
			glTranslatef( gWidth - 300 - 100, 300, 0 );
			glBindTexture( GL_TEXTURE_2D, Game::nStart );
			glBegin( GL_QUADS );
				glTexCoord2i( 0, 0 ); glVertex2i( 0, 0 );
				glTexCoord2i( 1, 0 ); glVertex2i( 300, 0 );
				glTexCoord2i( 1, 1 ); glVertex2i( 300, 100 );
				glTexCoord2i( 0, 1 ); glVertex2i( 0, 100 );
			glEnd( );
		glPopMatrix( );
	glPopAttrib( );
		
	glPushAttrib( GL_CURRENT_BIT | GL_TEXTURE_BIT  );
		if( nMainMenuSelection == 0 ) glColor4f( 0.5f, 0.5f, 0.5f, 1.0f );
		else glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );

		glPushMatrix( );
			glBindTexture( GL_TEXTURE_2D, Game::nExit );
				glTranslatef( gWidth - 300 - 100, 300 - 100, 0 );
			glBegin( GL_QUADS );
				glTexCoord2i( 0, 0 ); glVertex2i( 0, 0 );
				glTexCoord2i( 1, 0 ); glVertex2i( 300, 0 );
				glTexCoord2i( 1, 1 ); glVertex2i( 300, 100 );
				glTexCoord2i( 0, 1 ); glVertex2i( 0, 100 );
			glEnd( );
		glPopMatrix( );
	glPopAttrib( );


	glMatrixMode( GL_PROJECTION );
	glPopMatrix( );
	glMatrixMode( GL_MODELVIEW );
}

void renderInGame( )
{
	glPushAttrib( GL_TEXTURE_BIT );
	glPushMatrix( );
		glTranslatef( Game::RESOLUTION_WIDTH - Game::HUD_WIDTH, 0, 0 );		
		glBindTexture( GL_TEXTURE_2D, Game::nHud );
		glCallList( Game::nHudDisplayList );
	glPopMatrix( );
	glPopAttrib( );

	for( unsigned int i = 0; i < GAME_BOARD_SIZE * GAME_BOARD_SIZE; i++ )
	{
		Game::gameBoard[ i ]->render( );
	}

	Game::pPlayer->render( );

	
	for( unsigned int i = 0; i < nNumberOfGhosts; i++ )
		ghosts[ i ]->render( );


	/* Write text */
	ostringstream ossPlayerLives;
	ossPlayerLives << setw( 16 ) << setfill( '0' ) << Game::nPlayerLives;
	writeText( GLUT_BITMAP_HELVETICA_18, ossPlayerLives.str( ), Game::RESOLUTION_WIDTH - 210, 168 );

	ostringstream ossScore;
	ossScore << setw( 16 ) << setfill( '0' ) << Game::nScore;
	writeText( GLUT_BITMAP_HELVETICA_18, ossScore.str( ), Game::RESOLUTION_WIDTH - 210, 95 );

	ostringstream ossLevel;
	ossLevel << setw( 16 ) << setfill( '0' ) << Game::nLevel;
	writeText( GLUT_BITMAP_HELVETICA_18, ossLevel.str( ), Game::RESOLUTION_WIDTH - 210, 20 );

	//writeText( GLUT_BITMAP_HELVETICA_18, std::string("Pac Man"), 2, 20 );
	//writeText( GLUT_BITMAP_8_BY_13, std::string("Press <ESC> to quit."), 2, 5 );
}

void renderGameOver( )
{
	glMatrixMode( GL_PROJECTION );
	glPushMatrix( );
	glLoadIdentity( );
	glOrtho( 0.0, gWidth, 0.0, gHeight, 0.0, 1.0 );
	glMatrixMode( GL_MODELVIEW );

	glPushMatrix( );
		glLoadIdentity( );

		glPushAttrib( GL_CURRENT_BIT | GL_TEXTURE_BIT );
			glBindTexture( GL_TEXTURE_2D, Game::nGameOver );

			glBegin( GL_QUADS );
				glTexCoord2i( 0, 0 ); glVertex2i( 0, 0 );
				glTexCoord2i( 1, 0 ); glVertex2i( gWidth, 0 );
				glTexCoord2i( 1, 1 ); glVertex2i( gWidth, gHeight );
				glTexCoord2i( 0, 1 ); glVertex2i( 0, gHeight );
			glEnd( );
		glPopAttrib( );
	glPopMatrix( );


	glMatrixMode( GL_PROJECTION );
	glPopMatrix( );
	glMatrixMode( GL_MODELVIEW );
}

void renderNextLevel( )
{
	glMatrixMode( GL_PROJECTION );
	glPushMatrix( );
	glLoadIdentity( );
	glOrtho( 0.0, gWidth, 0.0, gHeight, 0.0, 1.0 );
	glMatrixMode( GL_MODELVIEW );

	glPushMatrix( );
		glLoadIdentity( );

		glPushAttrib( GL_CURRENT_BIT | GL_TEXTURE_BIT );
			glBindTexture( GL_TEXTURE_2D, Game::nNextLevel );

			glBegin( GL_QUADS );
				glTexCoord2i( 0, 0 ); glVertex2i( 0, 0 );
				glTexCoord2i( 1, 0 ); glVertex2i( gWidth, 0 );
				glTexCoord2i( 1, 1 ); glVertex2i( gWidth, gHeight );
				glTexCoord2i( 0, 1 ); glVertex2i( 0, gHeight );
			glEnd( );
		glPopAttrib( );
	glPopMatrix( );


	glMatrixMode( GL_PROJECTION );
	glPopMatrix( );
	glMatrixMode( GL_MODELVIEW );

}

void setGhostSeekPacManMode( int g )
{
	Ghost::setAllGhostToSeek( );
}

void updateInGame( )
{
	for( unsigned int i = 0; i < GAME_BOARD_SIZE * GAME_BOARD_SIZE; i++ )
	{
		Game::gameBoard[ i ]->update( );
	}

	Game::pPlayer->update( );

	for( unsigned int i = 0; i < nNumberOfGhosts; i++ )
		ghosts[ i ]->update( );

	if( isLevelWinCondition( ) )
	{
		if( nLevel < Game::NUMBER_OF_LEVELS )
		{
			state = NEXT_LEVEL;
		}
		else
		{
			// TO DO: HAndle Win game condition better			
			state = MAIN_MENU;
		}
	}

}

void handleInGameInput( int key )
{
	switch( key )
	{
		case GLUT_KEY_LEFT:
			pPlayer->setOrientation( PacMan::LEFT );
			break;
		case GLUT_KEY_RIGHT:
			pPlayer->setOrientation( PacMan::RIGHT );
			break;
		case GLUT_KEY_UP:
			pPlayer->setOrientation( PacMan::UP );
			break;
		case GLUT_KEY_DOWN:
			pPlayer->setOrientation( PacMan::DOWN );
			break;
		default:
			break;
	}
}

void handleMainMenuInput( int key )
{

	switch( key )
	{
		case GLUT_KEY_UP:
			{
				Game::soundSystem->playSound( FMOD_CHANNEL_FREE, Game::soundEffects[ Game::SFX_BIG_DOT_EATEN ], false, 0 );
				nMainMenuSelection = 0;
				break;
			}
		case GLUT_KEY_DOWN:
			{
				Game::soundSystem->playSound( FMOD_CHANNEL_FREE, Game::soundEffects[ Game::SFX_BIG_DOT_EATEN ], false, 0 );
				nMainMenuSelection = 1;
				break;
			}
		case 13:
			{
				if( nMainMenuSelection == 1 )
				{
					deinitialize( );
					exit( EXIT_SUCCESS );
				}
				else 
				{
					// load the darn level already
					nLevel = 1;
					nPlayerLives = 3;
					nScore = 0;
					initializeLevel( );
					state = Game::IN_GAME;			
				}
				break;
			}
		default:
			break;
	}
}


void handleGameOverInput( int key )
{
	//nLevel = 1; // do we really want the player to start from the beginning?
	
	if( key == 13 )
	{
		if( isGameLoseCondition( ) )
		{
			state = Game::MAIN_MENU;
			
			// load the music for the main menu...
			FMOD_RESULT fmodResult = Game::soundSystem->createSound( "assets/msc/menu.mid", FMOD_LOOP_NORMAL | FMOD_2D | FMOD_HARDWARE, 0, &Game::music );	
			ERRCHECK(fmodResult);
			
			// start playing music...
			fmodResult = Game::soundSystem->playSound( FMOD_CHANNEL_REUSE, Game::music, false, &Game::musicChannel );
			ERRCHECK(fmodResult);
			Game::musicChannel->setVolume( 0.5f );
			
			fmodResult = Game::soundSystem->playSound( FMOD_CHANNEL_FREE, Game::soundEffects[ Game::SFX_GAME_OVER ], false, 0 );

			ERRCHECK(fmodResult);
		}
		else
		{
			initializeLevel( );
			state = Game::IN_GAME;
		}
	}
}

void handleNextLevelInput( int key )
{
	if( key == 13 )
	{	
		Game::soundSystem->playSound( FMOD_CHANNEL_FREE, Game::soundEffects[ Game::SFX_NEXT_LEVEL ], false, 0 );
		Game::nextLevel( );
	}
}


bool isLevelWinCondition( )
{ return Game::nDotCount <= 0; }


bool isGameWinCondition( )
{ return nLevel >= Game::NUMBER_OF_LEVELS; }


bool isGameLoseCondition( )
{ return nPlayerLives < 0; }

void nextLevel( )
{
	Game::nLevel++;

	// if we havn't won the game...
	//if( !isGameWinCondition( ) )
	//{
		Game::nPlayerLives++; // increase the players lives by 1

		Game::initializeLevel( );
		Game::state = Game::IN_GAME;
	//}
	//else // otherwise, he/she won and their prize is to exit coldly.
	//{
	//	deinitializeLevel( );
	//	deinitialize( );
	//	exit( 0 );
	//}
}


void initializeLevel( )
{
	ostringstream ossLevelFile;
	ossLevelFile << "assets/level" << setw( 2 ) << setfill( '0' ) << nLevel << ".lvl";
	loadLevel( ossLevelFile.str( ).c_str( ) );

	ostringstream ossMusicFile;
	ossMusicFile << "assets/msc/level" << setw( 2 ) << setfill( '0' ) << nLevel << ".mid";
	FMOD_RESULT fmodResult = Game::soundSystem->createSound( ossMusicFile.str( ).c_str( ), FMOD_LOOP_NORMAL | FMOD_2D | FMOD_HARDWARE, 0, &Game::music );	
	ERRCHECK(fmodResult);

	// start playing music...
	fmodResult = Game::soundSystem->playSound( FMOD_CHANNEL_REUSE, Game::music, false, &Game::musicChannel );
	ERRCHECK(fmodResult);
	Game::musicChannel->setVolume( 0.5f );
	ERRCHECK(fmodResult);

}

void deinitializeLevel( )
{
	unloadLevel( );
}

} //end of namespace
